//
//  HBProtocolDispatcher.m
//  HBLibary
//
//  Created by Rambo on 2019/3/1.
//

#import "HBProtocolDispatcher.h"
#import <objc/runtime.h>


#pragma mark -
#pragma mark -
#pragma mark - HBProtocolDispatcherWeakArray

@interface HBProtocolDispatcherWeakArray ()
@property (nonatomic, strong) NSPointerArray  *pointerArray;
@end


@implementation HBProtocolDispatcherWeakArray

- (instancetype)init {
    
    self = [super init];
    if (self) {
        [self setup];
    }
    return self;
}

- (void)setup {
    self.pointerArray = [NSPointerArray weakObjectsPointerArray];
}

- (void)addObject:(id)object {
    [self.pointerArray addPointer:(__bridge void *)(object)];
}

- (id)objectAtWeakMutableArrayIndex:(NSUInteger)index {
    return [self.pointerArray pointerAtIndex:index];
}

- (void)removeObject:(id)object{
    if (!object) return;
    for (NSInteger idx = 0; idx < self.pointerArray.allObjects.count; idx++) {
        id obj = self.pointerArray.allObjects[idx];
        if (obj == object) {
            [self.pointerArray removePointerAtIndex:idx];
            [self removeObject:object];
            break;
        }
    }
}

#pragma mark - 重写getter方法
@synthesize allObjects = _allObjects;
- (NSArray *)allObjects {
    return self.pointerArray.allObjects;
}

@synthesize usableCount = _usableCount;
- (NSInteger)usableCount {
    return self.pointerArray.allObjects.count;
}

@synthesize allCount = _allCount;
- (NSInteger)allCount {
    return self.pointerArray.count;
}

@end


#pragma mark -
#pragma mark -
#pragma mark - HBProtocolDispatcher


struct objc_method_description MethodDescriptionForSELInProtocol(Protocol *protocol, SEL sel) {
    struct objc_method_description description = protocol_getMethodDescription(protocol, sel, YES, YES);
    if (description.types) {
        return description;
    }
    description = protocol_getMethodDescription(protocol, sel, NO, YES);
    if (description.types) {
        return description;
    }
    return (struct objc_method_description){NULL, NULL};
}

BOOL ProtocolContainSel(Protocol *protocol, SEL sel) {
    return MethodDescriptionForSELInProtocol(protocol, sel).types ? YES: NO;
}


@interface ImplemertorContext : NSObject

@property (nonatomic, weak) id implemertor;

@end

@implementation ImplemertorContext

@end



@interface HBProtocolDispatcher ()

@property (nonatomic, strong) Protocol *prococol;
@property (nonatomic, copy) NSArray *implemertors;

@end


@implementation HBProtocolDispatcher
+ (id)dispatcherProtocol:(Protocol *)protocol toImplemertors:(HBProtocolDispatcherWeakArray *)implemertors {
    return [[HBProtocolDispatcher alloc] initWithProtocol:protocol toImplemertors:implemertors];
}

- (instancetype)initWithProtocol:(Protocol *)protocol toImplemertors:(HBProtocolDispatcherWeakArray *)implemertors {
    if (self = [super init]) {
        self.prococol = protocol;
        NSMutableArray *implemertorContexts = [NSMutableArray array];
        for (id implemertor in implemertors.allObjects) {
            ImplemertorContext *implemertorContext = [ImplemertorContext new];
            implemertorContext.implemertor = implemertor;
            [implemertorContexts addObject:implemertorContext];
            objc_setAssociatedObject(implemertor, _cmd, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
        self.implemertors = implemertorContexts;
    }
    return self;
}

- (BOOL)respondsToSelector:(SEL)aSelector {
    if (!ProtocolContainSel(self.prococol, aSelector)) {
        return [super respondsToSelector:aSelector];
    }
    
    for (ImplemertorContext *implemertorContext in self.implemertors) {
        if ([implemertorContext.implemertor respondsToSelector:aSelector]) {
            return YES;
        }
    }
    return NO;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (!ProtocolContainSel(self.prococol, aSelector)) {
        return [super methodSignatureForSelector:aSelector];
    }
    
    struct objc_method_description methodDescription = MethodDescriptionForSELInProtocol(self.prococol, aSelector);
    return [NSMethodSignature signatureWithObjCTypes:methodDescription.types];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL aSelector = anInvocation.selector;
    if (!ProtocolContainSel(self.prococol, aSelector)) {
        [super forwardInvocation:anInvocation];
        return;
    }
    
    for (ImplemertorContext *implemertorContext in self.implemertors) {
        if ([implemertorContext.implemertor respondsToSelector:aSelector]) {
            [anInvocation invokeWithTarget:implemertorContext.implemertor];
        }
    }
}
@end
